#include "Installer.h"
#include <Common.h>
#include <q7zip.h>
#include <QFile>
#include <QDir>
#include <QTextStream>
#include <QSettings>
#include <QApplication>
#include <QStandardPaths>
#include <Windows.h>
#include <shlobj.h>
#include "shlobj_core.h"
#include "shellapi.h"


Installer::Installer(QObject* parent)
{

}

Installer* Installer::instance()
{
	static Installer ins(nullptr);
	return &ins;
}

bool Installer::isAppRun()
{
	return false;
}

bool Installer::isAppExists(const QString& targetPath)
{
	return QFile::exists(QDir(targetPath).absoluteFilePath(Commom::InstallAppName));
}

bool Installer::terminateApp()
{
	QString processName;

	return false;
}

bool Installer::isInstallerRun()
{
	return false;
}

QString Installer::installPath()
{
	return QString();
}

void Installer::startInstall(const QString& targetPath)
{
	if (QDir(targetPath) == QDir(targetPath).root())
	{
		m_installPath = QDir(targetPath).absoluteFilePath(Commom::InstallAppName);
	}
	else {
		m_installPath = targetPath;
	}
	m_installThread = QThread::create([this]() {
		installThread();
		});
	m_installThread->start();
}

bool Installer::isInstalling()
{
	return m_installThread && m_installThread->isRunning();
}

void Installer::startUnInstall()
{
	QString path = readInstallLocation();
	if (path.isEmpty()) {
		path = QApplication::applicationDirPath();
	}
	m_installPath = path;
	removeShortcut();
	removeApplicationFiles();
	removeInstallRegkeys();
}

void Installer::createShortcut()
{
	QString fileName = QDir::toNativeSeparators(m_installPath + "/" + Commom::InstallAppName);

	QString desktopShortcut = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).append("/").append(Commom::InstallAppName + ".lnk");
	QFile::link(fileName, desktopShortcut);

	QString startMenuShortcut = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).append("/").append(Commom::InstallAppName + ".lnk");
	QFile::link(fileName, startMenuShortcut);
}

void Installer::removeShortcut()
{
	QString targetPath = QDir::toNativeSeparators(m_installPath + "/" + Commom::InstallAppName);

	// 遍历桌面快捷方式，找到指定程序的快捷方式，删除
	QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
	QString applicationPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);

	std::function<void(QString, QString)> removeFunc = [this](const QString& targetPath, const QString& path) {
		QDir desktopDir(path);
		QStringList filter;
		filter << "*.lnk";
		auto filelist = desktopDir.entryInfoList(filter, QDir::Files);
		for (auto fileinfo : filelist) {
			QString target = getShortcutPath(fileinfo.absoluteFilePath());
			qDebug() << fileinfo.absoluteFilePath() << " ==> " << target;
			if (target == targetPath) {
				qDebug() << "remove shortcut:" << fileinfo.absoluteFilePath();
				if (FALSE == ::DeleteFileW(fileinfo.absoluteFilePath().toStdWString().c_str())) {
					qWarning() << "remove shortcut:" << fileinfo.absoluteFilePath() << " failed," << GetLastError();
				}
			}
		}
		};
	removeFunc(targetPath, desktopPath);
	removeFunc(targetPath, applicationPath);
}

void Installer::startApp()
{
	QString executePath = QDir::toNativeSeparators(m_installPath + "/" + Commom::InstallAppName);
	ShellExecuteW(NULL, L"open", executePath.toStdWString().c_str(), NULL, NULL, SW_SHOW);
}

void Installer::init()
{
	::CoInitialize(NULL);
	QString machineId = readMachineID();
	if (machineId.isEmpty()) {
		QByteArray uniqueId = QSysInfo::machineUniqueId();
		machineId = QString(uniqueId);
		writeMachineID(machineId);
	}
	SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
}

void Installer::uninit()
{
	::CoUninitialize();
	if (!m_removeBatFilePath.isEmpty()) {
		ShellExecuteW(NULL, L"open", m_removeBatFilePath.toStdWString().c_str(), NULL, NULL, SW_HIDE);
	}
}

void Installer::writeInstallLocation(const QString& path)
{
	QSettings settings(Commom::UninstallKey, QSettings::NativeFormat);
	QString installLocation = QDir::toNativeSeparators(path);
	settings.setValue("InstallLocation", installLocation);
	settings.sync();
}

void Installer::writeUninstallString(const QString& path)
{
	QSettings settings(Commom::UninstallKey, QSettings::NativeFormat);
	settings.setValue("UninstallString", path);
	settings.sync();
}

void Installer::writeInstallerInfomation()
{
	QDir appDir(m_installPath);
	QSettings settings(Commom::UninstallKey, QSettings::NativeFormat);
	settings.setValue("DisplayName", Commom::InstallFolderName);
	settings.setValue("DisplayIcon", QDir::toNativeSeparators(appDir.absoluteFilePath(Commom::InstallAppName)));
	settings.setValue("DisplayVersion", Commom::DisplayVersion);
	settings.setValue("Publisher", "Everimaging Co., Ltd.");
	settings.sync();
}

void Installer::writeMachineID(const QString& machineID)
{
	QSettings settings(Commom::UninstallKey, QSettings::NativeFormat);
	settings.setValue("MachineID", machineID);
	settings.sync();
}

void Installer::removeInstallRegkeys()
{
	QSettings settings(Commom::UninstallRootKey, QSettings::NativeFormat);
	settings.remove(Commom::InstallFolderName);
}

void Installer::removeApplicationFiles()
{
	QString removeBatContent;
	removeBatContent.append("TIMEOUT /T 1 \r\n");

	QDir tempPath = QDir::temp();
	QString batFilePath = tempPath.absoluteFilePath(Commom::InstallFolderName + "-uninstall.bat");

	QString rootPath = QString("rmdir \"%1\" /s /q \r\n").
		arg(QDir::toNativeSeparators(m_installPath));
	removeBatContent.append(rootPath);

	QString delBatFile = QString("del \"%1\" /q /f \r\n")
		.arg(QDir::toNativeSeparators(batFilePath));
	removeBatContent.append(delBatFile);

	QFile file(batFilePath);
	if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
		return;
	}
	QTextStream out(&file);
	out << removeBatContent;
	// 关闭文件
	file.close();
	m_removeBatFilePath = batFilePath;
}

QString Installer::readInstallLocation() const
{
	QSettings settings(Commom::UninstallKey, QSettings::NativeFormat);
	return settings.value("InstallLocation", "").toString();;
}

QString Installer::readUninstallString() const
{
	QSettings settings(Commom::UninstallKey, QSettings::NativeFormat);
	return settings.value("UninstallString", "").toString();
}

QString Installer::readMachineID() const
{
	QSettings settings(Commom::UninstallKey, QSettings::NativeFormat);
	return settings.value("MachineID", "").toString();
}

bool Installer::installThread()
{
	if (Q7Zip::getInstance()->init() != 0) {
		qWarning() << "7z init failed";
		emit installFinished(false);
		return false;
	}
	connect(Q7Zip::getInstance(), &Q7Zip::extract_completeValue_signal, this, [this](quint64 completeValue) {
		int progress = m_extractTotalSize != 0 ? completeValue * 1.0 * 100 / m_extractTotalSize : 10;
		emit installProgress(progress);
		}, Qt::DirectConnection);

	connect(Q7Zip::getInstance(), &Q7Zip::extracting_filename_signal, this, [this](const QString& filename) {
		qDebug() << "解压文件:" << filename;
		}, Qt::DirectConnection);

	connect(Q7Zip::getInstance(), &Q7Zip::extract_filesize_signal, this, [this](quint64 filesize) {
		qDebug() << "解压文件大小:" << filesize;
		m_extractTotalSize = filesize;
		}, Qt::DirectConnection);

	QDir archiveDir(QApplication::applicationDirPath());
	QString archivePath = archiveDir.filePath("app.7z");
	if (!QFileInfo::exists(archivePath)) {
		qWarning() << "app.7z文件不存在:" << archivePath;
		emit installFinished(false);
		return false;
	}
	// 解压文件夹
	if (Q7Zip::getInstance()->extract(archivePath, m_installPath) != 0) {
		qWarning() << "extract file failed.";
		emit installFinished(false);
		return false;
	}
	// 在注册表中写入安装位置
	writeInstallLocation(m_installPath);

	// 复制卸载文件到安装目录中
	QString curAppFilePath = QApplication::applicationFilePath();
	QString uninstallFilePath = m_installPath + "/uninstall.exe";
	if (!QFile::rename(curAppFilePath, uninstallFilePath)) {
		emit installFinished(false);
	}

	QString uninstallCmd = QString("%1 --mode=uninstall").arg(QDir::toNativeSeparators(uninstallFilePath));
	writeUninstallString(uninstallCmd);
	writeInstallerInfomation();
	emit installFinished(true);
	return true;
}

QString Installer::getShortcutPath(const QString& shortcut)
{
	QString result;
	IShellLink* psl = NULL;
	//创建COM接口，IShellLink对象创建
	HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
	if (SUCCEEDED(hr))
	{
		IPersistFile* ppf;
		hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
		if (SUCCEEDED(hr))
		{
			hr = ppf->Load(shortcut.toStdWString().c_str(), STGM_READ);    //加载文件
			if (SUCCEEDED(hr))
			{
				WIN32_FIND_DATA wfd;
				WCHAR ShellPath[MAX_PATH];
				psl->GetPath(ShellPath, MAX_PATH, (WIN32_FIND_DATA*)&wfd, SLGP_SHORTPATH);  //获取目标路径
				result = QString::fromWCharArray(ShellPath);
			}
			ppf->Release();
		}
		psl->Release();  //释放对象
	}

	if (result.contains('~')) {
		WCHAR longPath[MAX_PATH] = { 0 };
		GetLongPathNameW(result.toStdWString().c_str(), longPath, sizeof(longPath) / sizeof(TCHAR));
		result = QString::fromWCharArray(longPath);
	}
	return result;
}


